package io.gitee.chearnee.fw.datasecurity.core.advice;

import io.gitee.chearnee.fw.common.utils.BeanCloneUtil;
import lombok.Getter;
import lombok.SneakyThrows;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.ResolvableType;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

/**
 * @author cn
 * @version 1.0
 * @description
 * @date 12/2/2022 4:59 PM
 */
public abstract class DataSecurityAop<T extends Annotation, D extends Annotation> {
    private final Class<T> annotationType;

    protected DataSecurityAop(Class<T> annotation) {
        this.annotationType = annotation;
    }


    public abstract void pointcut();

    public abstract void disablePointcut();

    public abstract boolean in(D t);

    public abstract boolean out(D t);

    public abstract Object inExecute(SDEntity target);

    public abstract Object outExecute(Object target, Annotation annotation);

    public abstract <B extends Annotation> Optional<B> findAnnotation(Annotation[] annotations);

    @Around("!disablePointcut()&&pointcut()")
    public Object around(ProceedingJoinPoint joinPoint) {
        return dataSecurityProcess(joinPoint);
    }

    @SneakyThrows
    private Object dataSecurityProcess(ProceedingJoinPoint joinPoint) {
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        Method method = methodSignature.getMethod();
        D enableSDAop = checkAop(method);
        if (enableSDAop == null) {
            return joinPoint.proceed();
        }
        Object[] args = joinPoint.getArgs();

        if (in(enableSDAop)) {
            args = parameterProcessor(args, method);
        }
        if (out(enableSDAop)) {
            return returnCryptProcessor(joinPoint.proceed(args), method);
        }
        return joinPoint.proceed(args);
    }


    private Object returnCryptProcessor(Object returnObj, Method method) {
        if (returnObj == null) {
            return null;
        }
        Annotation annotation = method.getAnnotation(annotationType);
        return outExecute(returnObj, annotation);
    }


    /**
     * 主要是获取参数上的注解，如果是直接传entity的，参数上不用写也可以会进行后续处理
     *
     * @param sArgs
     * @param method
     * @return
     */
    private Object[] parameterProcessor(Object[] sArgs, Method method) {

        Object[] args = (Object[]) BeanCloneUtil.deepClone(sArgs);
        Annotation[][] params = method.getParameterAnnotations();
        List<SDEntity> sdEntities = new ArrayList<>(args.length);
        for (int i = 0; i < params.length; i++) {
            Object arg = args[i];
            Annotation[] param = params[i];
            if (arg == null) {
                SDEntity sdEntity = new SDEntity();
                sdEntity.annotation = null;
                sdEntity.obj = null;
                sdEntities.add(sdEntity);
                continue;
            }
            if (param.length == 0) {
                SDEntity sdEntity = new SDEntity();
                sdEntity.annotation = null;
                sdEntity.obj = arg;
                sdEntities.add(sdEntity);
                continue;
            }
            Optional<Annotation> first = findAnnotation(param);
            if (first.isPresent()) {
                Annotation annotation = first.get();
                SDEntity sdEntity = new SDEntity();
                sdEntity.annotation = annotation;
                sdEntity.obj = arg;
                sdEntities.add(sdEntity);
            } else {
                SDEntity sdEntity = new SDEntity();
                sdEntity.annotation = null;
                sdEntity.obj = arg;
                sdEntities.add(sdEntity);
            }
        }
        if (sdEntities.isEmpty()) {
            return sArgs;
        }
        return sdEntities.stream().map(e -> {
            if (e.obj != null) {
                e.obj = inExecute(e);
            }
            return e.obj;
        }).toArray();
    }

    /**
     * 获取第二个泛型注解
     *
     * @param method
     * @return
     */
    private D checkAop(Method method) {
        Class<? extends Annotation> aClass = (Class<? extends Annotation>) ResolvableType.forClass(this.getClass()).getSuperType().getSuperType().resolveGeneric(1);
        D enableSDAop = (D) method.getAnnotation(aClass);
        if (enableSDAop == null) {
            enableSDAop = (D) method.getDeclaringClass().getAnnotation(aClass);
        }
        return enableSDAop;
    }


    @Getter
    protected static class SDEntity {
        private Object obj;
        private Annotation annotation;
    }
}
